<?php

/**
 * Simple paypal API with NVP (Name Value Parsing) method.
 * 
 * @author GusDeCooL <gusdecool@gmail.com>
 * @link https://code.google.com/p/simple-paypal-api/
 * @version 1.0
 */
Class Paypal {
	private $_username;
	private $_password;
	private $_signature;
	private $_method;
	private $_version = '87.0';
	
	/**
	 * Paypal environment
	 * @var string
	 */
	private $_environment = 'live';
	
	/**
	 * Paypal response
	 * @var array
	 */
	private $_response;
	
	/**
	 * Paypal command
	 * @var array
	 */
	private $_command;
	
	/**
	 * Contains information each product
	 * @var array
	 */
	private $_product;
	
	/**
	 * Paypal token
	 * @var string
	 */
	private $_token;
	
	CONST SERVER_SANDBOX = 'https://api-3t.sandbox.paypal.com/nvp';
	CONST SERVER_LIVE = 'https://api-3t.paypal.com/nvp';
	
	CONST SERVER_CHECKOUT_SANDBOX = 'https://www.sandbox.paypal.com/webscr';
	CONST SERVER_CHECKOUT_LIVE = 'https://www.live.paypal.com/webscr';
	
	/**
	 * Available paypal method
	 * @var array
	 */
	static public $availableMethod = array(
		'GetBalance',
		'SetExpressCheckout',
		'DoExpressCheckoutPayment'
	);
	
	/**
	 * Construct object
	 * @param string $username Api username
	 * @param string $password Api password
	 * @param string $signature Api signature
	 */
	public function __construct($username, $password, $signature) {
		$this->_username = $username;
		$this->_password = $password;
		$this->_signature = $signature;
	}
	
	/**
	 * Set paypal environment
	 * @param string $env Paypal api environment. Possible values: 'Live', 'Sandbox'. Case-insensitive
	 */
	public function setEnvironment($env) {
		$env = strtolower($env);
		
		switch($env) {
			case 'live':
				$this->_environment = 'live';
				break;
			case 'sandbox':
				$this->_environment = 'sandbox';
				break;
			default:
				echo 'Wrong parameter for environment';
				break;
		}
	}
	
	/**
	 * Get account balance <br />
	 * currently this will only return your default curreny
	 * 
	 * @return double
	 * @todo get all curreny balance
	 */
	public function getBalance() {
		$this->_setMethod('GetBalance');
		
		if($this->_execute()) {
			return (double) $this->_response['L_AMT0'];
		} else {
			echo 'There is some error retrieving balance amount';
		}
		
	}
	
	private function _setMethod($method) {
		if (in_array($method, self::$availableMethod)) {
			$this->_method = $method;
		} else {
			echo 'This method not yet available';
		}
		
	}
	
	/**
	 * Execute request to paypal server
	 * 
	 * @return bool return true if success
	 */
	private function _execute() {
		$this->_initCommandCredential();
		$cUrl = curl_init();
		curl_setopt($cUrl, CURLOPT_URL, $this->_getServerUrl());
		curl_setopt($cUrl, CURLOPT_VERBOSE, 1);
		curl_setopt($cUrl, CURLOPT_SSL_VERIFYPEER, FALSE);
		curl_setopt($cUrl, CURLOPT_SSL_VERIFYHOST, FALSE);
		curl_setopt($cUrl, CURLOPT_RETURNTRANSFER, 1);
		curl_setopt($cUrl, CURLOPT_POST, 1);
		curl_setopt($cUrl, CURLOPT_POSTFIELDS, $this->_paypalSendCommand());
		$response = curl_exec($cUrl);
		$this->_setPaypalResponse($response);
		curl_close($cUrl);		
		
		if($this->_response['ACK'] == 'Success') {
			return true;
		} else if ($this->_response['ACK'] == 'Failure') {
			return false;
		}
	}
	
	/**
	 * return paypal send command
	 * @return string
	 */
	private function _paypalSendCommand() {
		$concatCommand = array();
		foreach($this->_getCommand(true) as $key => $value) {
			$concatCommand[] = $key.'='.$value;
		}
		
		return implode('&', $concatCommand);
	}
	
	/**
	 * Get command
	 * @param bool $encoded Do URL Encoded or not
	 * @return array 
	 */
	private function _getCommand($encoded = false) {
		$return = array();
		if(!is_array($this->_command)) return $return;
		
		if($encoded === true) {
			$return = self::_urlEncodeArray($this->_command);
		} else {
			$return = $this->_command;
		}
		
		return $return;
	}
	
	/**
	 * Get paypal API server url
	 * @return string
	 */
	private function _getServerUrl() {
		switch($this->_environment) {
			case 'live':
				return self::SERVER_LIVE;
				break;
			case 'sandbox':
				return self::SERVER_SANDBOX;
				break;
		}
	}
	
	/**
	 * Url encode an array
	 * @param array $array array to encoded
	 * @return array Return empty array if failure
	 */
	static private function _urlEncodeArray($array) {
		$return = array();
		if(!is_array($array)) return $return;
		
		foreach($array as $key => $value) {
			$return[$key] = urlencode($value);
		}
		
		return $return;
	}
	
	/**
	 * Url decode an array
	 * @param array $array array to decoded
	 * @return array Return empty array if failure
	 */
	static private function _urlDecodeArray($array) {
		$return = array();
		if(!is_array($array)) return $return;
		
		foreach($array as $key => $value) {
			$return[$key] = urldecode($value);
		}
		
		return $return;
	}
	
	/**
	 * Initialize credential commands
	 */
	private function _initCommandCredential() {
		$this->_addCommand('METHOD', $this->_method);
		$this->_addCommand('USER', $this->_username);
		$this->_addCommand('PWD', $this->_password);
		$this->_addCommand('SIGNATURE', $this->_signature);
		$this->_addCommand('VERSION', $this->_version);		
	}
	
	/**
	 * Set paypal command
	 * @param string $key Command Key
	 * @param string $value Command Value
	 */
	private function _addCommand($key, $value) {
		$this->_command[$key] = $value;
	}
	
	/**
	 * Set property response from paypal
	 * @param string $response Paypal response
	 */
	private function _setPaypalResponse($response) {
		$responseArray = explode('&', $response);
		$return = array();
		
		foreach($responseArray as $value) {
			$explodeResponse = explode('=', $value);
			$return[$explodeResponse[0]] = $explodeResponse[1];
		}
		
		$this->_response =  self::_urlDecodeArray($return);
	}
	
	/**
	 * Add product to paypal invoice, this usually used before set express checkout
	 * @param string $name Product name.
	 * @param double $price Product each price.
	 * @param double $qty Product Quantity, default 1.
	 */
	public function addProduct($name, $price, $qty=1) {
		$this->_product[] = array(
			'name' => $name,
			'price' => (double)$price,
			'qty' => (double)$qty,
		);
	}
	
	/**
	 * Request express checkout to paypal
	 * 
	 * @param string $returnUrl Return url if customer pay
	 * @param string $cancelUrl Return url if customer cancel payment
	 * @return string Return token from paypal
	 */
	public function reqExpressCheckout($returnUrl, $cancelUrl) {
		$this->_setMethod('SetExpressCheckout');
		$this->_addCommand('RETURNURL', $returnUrl);
		$this->_addCommand('CANCELURL', $cancelUrl);
		$this->_addCommand('AMT', $this->getTotalPayment());
		
		$i = 0;
		foreach($this->_product as $value) {
			$this->_addCommand('L_NAME'.$i, $value['name']);
			$this->_addCommand('L_AMT'.$i, $value['price']);
			$this->_addCommand('L_QTY'.$i, $value['qty']);
			$i++;
		}
		
		if($this->_execute()) {
			$this->_token = $this->_response['TOKEN'];
			return $this->_token;
		} else {
			echo 'There is some error requesting checkout to paypal';
			return false;
		}
	}
	
	/**
	 * Get total payment amount
	 * @return double
	 */
	public function getTotalPayment() {
		$total = (double)0;
		foreach($this->_product as $value) {
			$total += $value['price'] * $value['qty'];
		}
		
		return $total;
	}
	
	/**
	 * Get checkout url
	 * @return string Return false if token not setup
	 */
	public function getExpressCheckoutUrl() {
		if($this->_token === null) return false;
		
		switch ($this->_environment) {
			case 'live':
				$url = self::SERVER_CHECKOUT_LIVE;
				break;
			case 'sandbox':
				$url = self::SERVER_CHECKOUT_SANDBOX;
				break;
		}
		
		$urlParams = '?cmd=_express-checkout&token='.$this->_token;
		return $url.$urlParams;
	}
	
	/**
	 * Confirm express checkout payment
	 * @param string $token Paypal Token
	 * @param string $payerId Paypal payer ID
	 * @param double $amount Transaction total amount
	 * @param string $paymentAction Paypal payment action. Possible argument: 'Authorization', 'Order', 'Sale'
	 * 
	 * @return bool 
	 */
	public function confirmExpressCheckout($token, $payerId, $amount, $paymentAction='Sale') {
		$this->_setMethod('DoExpressCheckoutPayment');
		$this->_addCommand('TOKEN', $token);
		$this->_addCommand('PAYERID', $payerId);
		$this->_addCommand('AMT', $amount);
		$this->_addCommand('PAYMENTACTION', $paymentAction);
		
		if($this->_execute()) {
			return true;
		} else {
			return false;
		}
	}
}

?>